﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using Public.Ocean.Petrel.Extensions.Internal;

namespace Public.Ocean.Petrel.Extensions.Stream
{
    public class Schema<TData> : IEnumerable<int>
    {
        string type;
        int version;

        List<string> properties;
        List<Func<TData, string>> getters;
        List<Action<TData, string>> setters;

        StringValueConverter converter;

        public Schema(string type, int version)
        {
            this.type = type;
            this.version = version;

            this.properties = new List<string>();
            this.getters = new List<Func<TData, string>>();
            this.setters = new List<Action<TData, string>>();

            this.converter = new StringValueConverter();
        }

        public void Add<TValue>(string name, Func<TData, TValue> getter, Action<TData, TValue> setter, TValue valueIfMissing)
        {
            properties.Add(name);
            getters.Add(d => converter.Pack(getter(d)));
            setters.Add((d, v) => setter(d, converter.Unpack(v, valueIfMissing)));
        }

        public void Add<TValue>(Expression<Func<TData, TValue>> getterExpression, Action<TData, TValue> setter)
        {
            Add(getterExpression.ExtractName(), getterExpression.Compile(), setter, default(TValue));
        }

        public void Add<TValue>(string name, Func<TData, TValue> getter, Action<TData, TValue> setter, Func<TData, TValue, string> identify, Func<TData, string, TValue> resolve)
        {
            properties.Add(name);
            getters.Add(d => identify(d, getter(d)));
            setters.Add((d, v) => setter(d, resolve(d, v)));
        }

        public IEnumerable<KeyValuePair<string, string>> Pack(TData data)
        {
            for (int i = 0; i < properties.Count; i++)
            {
                yield return new KeyValuePair<string, string>(properties[i], getters[i](data));
            }
        }

        public void Unpack(TData data, IEnumerable<KeyValuePair<string, string>> values)
        {
            for (int i = 0; i < properties.Count; i++)
            {
                string value = values.FirstOrDefault(e => e.Key == properties[i]).Value;
                setters[i](data, value);
            }
        }

        #region Enumerable dummy

        IEnumerator<int> IEnumerable<int>.GetEnumerator()
        {
            return Enumerable.Range(0, properties.Count).GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return Enumerable.Range(0, properties.Count).AsEnumerable().GetEnumerator();
        }
        #endregion
    }
}